﻿/*
* Copyright 2009-2010 AKABANA.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 
* either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text.RegularExpressions;
using System.Windows;
using Akabana.Amf.Amf0;
using Akabana.Amf.Amf0.IO;
using Akabana.Amf.Messaging.IO;
using Akabana.Amf.Messaging.Data;
using Akabana.Amf.Serializer;

namespace Akabana.Amf.Messaging.Component
{
    public delegate void InvokeErrorCallBack(System.Exception e);

    public class AmfConnection : DependencyObject
    {
        private static HttpWebRequest CreateHttpWebRequest(Uri uri)
        {
            var newHttpWebRequest = (HttpWebRequest)HttpWebRequest.Create(uri);
            newHttpWebRequest.Method = "POST";
            newHttpWebRequest.ContentType = "application/x-amf";
            return newHttpWebRequest;
        }

        private static AmfMessage CreateAmfMessage(string response, string command, object[] args)
        {
            var message = AmfMessage.CreateInstance(3);
            message.Initialize(0, 1);
            var body = AmfMessageBody.CreateInstance(command, response);
            body.data = args;
            message.AddBody(body);
            return message;
        }

        private static AmfMessage DoParseRequest(Stream responseStream)
        {
            AmfMessage message;
            using (var reader = new AmfMessageReader(responseStream))
            {
                message = AmfMessage.CreateInstance(reader.version);

                var headerCount = reader.ReadHeaderCount();
                var bodyCount = reader.ReadBodyCount();
                message.Initialize(headerCount, bodyCount);

                for (var i = 0; i < message.bodyCount; i++)
                {
                    string target = reader.ReadTarget();
                    string responsePath = reader.ReadResponse();

                    var body = AmfMessageBody.CreateInstance(target, responsePath);

                    uint messageSize = reader.ReadMessageSize();


                    body.data = reader.ReadObject();

                    message.AddBody(body);
                }
            }

            return message;
        }

        private Uri uri;

        private int connectionIndex;

        private Dictionary<string, AmfMessageRequest> responseToRequestMap;

        private Dictionary<HttpWebRequest, string> requestToResponseMap;

        public InvokeErrorCallBack InvokeErrorCallBack;

        public AmfConnection()
        {
        }

        public void Connect(Uri uri)
        {
            this.uri = uri;
            connectionIndex = 0;
            responseToRequestMap = new Dictionary<string, AmfMessageRequest>();
            requestToResponseMap = new Dictionary<HttpWebRequest, string>();
        }

        public void Call(string command, Responder responder, params object[] args)
        {

            int newIndex = ++connectionIndex;
            string newResponse = "/" + newIndex;

#if DEBUG
            System.Diagnostics.Debug.WriteLine("index={0},response={1},url={2},command={3}", newIndex, newResponse, uri.ToString(), command);
#endif
            var newHttpWebRequest = CreateHttpWebRequest(uri);
            var amfMessage = CreateAmfMessage(newResponse, command, args);
            var amfMessageRequest = AmfMessageRequest.NewInstance(newHttpWebRequest, responder, amfMessage);

            try
            {
                newHttpWebRequest.BeginGetRequestStream(new AsyncCallback(GetRequestStreamCallback), amfMessageRequest);
                responseToRequestMap.Add(newResponse, amfMessageRequest);
                requestToResponseMap.Add(newHttpWebRequest, newResponse);
            }
            catch (System.Exception e)
            {
                DoInvokeErrorCallBack(e);
            }
        }

        private void GetRequestStreamCallback(IAsyncResult asynchronousResult)
        {
            var amfMessageRequest = (AmfMessageRequest)asynchronousResult.AsyncState;
            var httpWebRequest = amfMessageRequest.Request;
            var amfMessage = amfMessageRequest.Message;

            try
            {

                using (var stream = httpWebRequest.EndGetRequestStream(asynchronousResult))
                {
                    using (var writer = new AmfMessageWriter(stream,3))
                    {
                        using (var dataWriter = new MemoryStream())
                        {
                            var dataAmfWriter = new AmfMessageWriter(dataWriter, 3);
                            writer.WriteVersion(amfMessage.version);
                            writer.WriteHeaderCount(amfMessage.headerCount);
                            writer.WriteBodyCount(amfMessage.bodyCount);

                            for (var i = 0; i < amfMessage.bodyCount; i++)
                            {
                                var body = amfMessage.GetBodyAt(i);
                                writer.WriteTarget(body.target);
                                writer.WriteResponse(body.response);

                                object[] args;
                                uint numArgument = 0;
                                if (body.data.GetType().IsArray)
                                {
                                    args = body.data as object[];
                                    numArgument = (uint)args.Length;
                                }
                                else
                                {
                                    args = new object[] { body.data };
                                    numArgument = 1;
                                }
                                for (var j = 0; j < numArgument; j++)
                                {
                                    dataAmfWriter.WriteObject(args[j]);
                                }
                                writer.WriteMessageSize((uint)(dataWriter.Length + 5));
                                writer.WriteTypeMarker(Amf0TypeMarker.StrictArrayMarker);
                                writer.WriteU32(numArgument);
                                writer.Write(dataWriter.ToArray());
                            }
                        }
                        writer.Flush();
                    }
                }

                httpWebRequest.BeginGetResponse(new AsyncCallback(ResponseReceiveCallback), httpWebRequest);
            }
            catch (System.Exception e)
            {
                ClearRequest(httpWebRequest);
                DoInvokeErrorCallBack(e);
            }
        }

        private void ResponseReceiveCallback(IAsyncResult asynchronousResult)
        {
            var httpWebRequest = (HttpWebRequest)asynchronousResult.AsyncState;
            requestToResponseMap.Remove(httpWebRequest);

            try
            {
                var response = (HttpWebResponse)httpWebRequest.EndGetResponse(asynchronousResult);
                using (var responseStream = response.GetResponseStream())
                {
                    var amfMessage = DoParseRequest(responseStream);
                    for (int i = 0; i < amfMessage.bodyCount; i++)
                    {
                        var body = amfMessage.GetBodyAt(i);

                        var matchesGroups = Regex.Match(body.target, @"(/.+)(/.+)").Groups;
                        var index = matchesGroups[1].Value;
                        var postfix = matchesGroups[2].Value;

                        AmfMessageRequest amfMessageRequest;
                        if (responseToRequestMap.TryGetValue(index, out amfMessageRequest))
                        {
                            var responder = amfMessageRequest.Responder;
                            responder.Response(postfix, body.data);
                        }
                        responseToRequestMap.Remove(index);
                        requestToResponseMap.Remove(httpWebRequest);
                    }
                    responseStream.Close();
                }

            }
            catch (System.Exception e)
            {
                ClearRequest(httpWebRequest);
                DoInvokeErrorCallBack(e);
            }
        }

        private void ClearRequest(HttpWebRequest httpWebRequest)
        {
            if (httpWebRequest != null)
            {
                string response;
                if(requestToResponseMap.TryGetValue(httpWebRequest,out response))
                {
                    responseToRequestMap.Remove(response);
                    requestToResponseMap.Remove(httpWebRequest);
                }
            }
        }

        private void DoInvokeErrorCallBack(System.Exception e)
        {
            Dispatcher.BeginInvoke(
                () =>
                {
                    if (InvokeErrorCallBack != null)
                    {
                        InvokeErrorCallBack(e);
                    }
                }
            );
        }
    }
}
